<?php

/**
 * Module manager file
 *
 * @author coder_
 */

final class _ModuleManager
{
    private static $instance;

    private $currentClasses;

    private $moduleList;

    private function __construct()
    {
    }

    protected function __clone()
    {
    }

    /**
     * Return instance of module manager.
     * If needed will create an instance before returning
     *
     * @static
     * @return
     */
    public static function getInstance()
    {
        if (is_null(self::$instance)) {
            self::$instance = new self;
        }
        return self::$instance;
    }

    /**
     * Initialize module manager
     *
     * @return void
     */
    public function initialize()
    {
        // save start pointing classes list
        $this->saveCurrentClasses();

        // get available modules list
        $fileNames = $this->getAvailableModules();

        // include module files
        $this->includeFiles($fileNames);

        // get module classes names
        $classNames = $this->getNewModuleClasses();

        // instantiate modules
        $this->createModules($classNames);

        // sort module class names by their dependencies
        $classNames = $this->sortModules($classNames);

        // save class name list
        $this->moduleList = $classNames;

        // initialize all modules
        $this->initializeModules($classNames);
    }

    /**
     * Return loaded modules list
     *
     * @return
     */
    public function getModulesList()
    {
        return $this->moduleList;
    }

    /**
     * Check if module with given name is loaded
     *
     * @param $moduleName
     * @return bool
     */
    public function isLoaded($moduleName)
    {
        return in_array($moduleName, $this->getModulesList());
    }

    private function getModule($name)
    {
        if (in_array($name, $this->moduleList)) return call_user_func($name . '::getInstance');
        else return null;
    }

    /**
     * Initialize modules
     *
     * @param $classNames
     * @return void
     */
    private function initializeModules($classNames)
    {
        foreach ($classNames as $className)
        {
            call_user_func($className . '::getInstance')->initialize();
        }
    }

    // TODO later optimize module sorting to remove these globals
    private $sortedModules = array();
    private $visitedModules = array();

    /**
     * Sort module class names by their dependencies
     *
     * @param $classNames
     * @return array
     */
    private function sortModules($classNames)
    {
        $noinc = array();

        $allneeded = array();

        foreach($classNames as $className)
        {
            $deps = call_user_func($className . '::getInstance')->getDependencies();

            $allneeded = array_merge($deps, $allneeded);
        }

        $allneeded = array_unique($allneeded);

        foreach($classNames as $className)
        {
            if(!in_array($className, $allneeded))
            {
                $noinc []= $className;
            }
        }

        foreach($noinc as $mod)
        {
            $this->modSorter($mod);
        }

        $classNames = $this->sortedModules;

        unset($this->sortedModules);
        unset($this->visitedModules);

        return $classNames;
    }

    private function modSorter($mod)
    {
        if(in_array($mod, $this->visitedModules)) return;

        $this->visitedModules []= $mod;

        $deps = call_user_func($mod . '::getInstance')->getDependencies();

        foreach($deps as $dep)
        {
            $this->modSorter($dep);
        }

        $this->sortedModules []= $mod;
    }

    /**
     * Instantiate modules
     *
     * @param $classNames
     * @return void
     */
    private function createModules($classNames)
    {
        foreach ($classNames as $className)
        {
            call_user_func($className . '::getInstance');
        }
    }

    /**
     * Return a list of available modules
     *
     * @return array
     */
    private function getAvailableModules()
    {
        $handle = opendir(MODULES);

        $fileNames = array();

        while (false !== ($file = readdir($handle))) {
            if (pathinfo($file, PATHINFO_EXTENSION) == 'php') {
                $fileNames[] = $file;
            }
        }

        return $fileNames;
    }

    /**
     * Include module files
     *
     * @param $fileNames
     * @return void
     */
    private function includeFiles($fileNames)
    {
        foreach ($fileNames as $fileName)
        {
            include_once(MODULES . '/' . $fileName);
        }
    }

    /**
     * Save current list of available classnames
     *
     * @return void
     */
    private function saveCurrentClasses()
    {
        $this->currentClasses = get_declared_classes();
    }

    /**
     * Return available module classnames
     *
     * @return array
     */
    private function getNewModuleClasses()
    {
        $newClasses = get_declared_classes();
        $moduleClasses = array_diff($newClasses, $this->currentClasses);
        unset($this->currentClasses);

        foreach ($moduleClasses as $key => $moduleClass)
        {
            if (!in_array('_Module', class_parents($moduleClass))) {
                unset($moduleClasses[$key]);
            }
        }

        return array_values($moduleClasses);
    }

    /**
     * Return module by given name
     *
     * @param $name
     * @return object
     */
    public function __get($name)
    {
        return $this->getModule($name);
    }
}